Concepte fundamentale de limbaje de programare

4. Incepe. Blocurile de construcție ale programului C# #2

4.1 Supraîncărcarea metodelor

Supraîncărcarea metodelor permite ca mai multe metode din aceeași clasă să aibă același nume atât timp cât au semnături unice. La compilarea unei invocări a unei metode supraîncărcate, compilatorul folosește rezoluția supraîncărcării pentru a determina metoda specifică de invocat. Rezoluția supraîncărcării găsește singura metodă care se potrivește cel mai bine cu argumentele. Dacă nu poate fi găsită o singură potrivire, este raportată o eroare. Următorul exemplu arată rezoluția de supraîncărcare în vigoare. Comentariul pentru fiecare invocare din metoda UsageExample arată ce metodă este invocată.

class OverloadingExample
{
    static void F() => Console.WriteLine("F()");
    static void F(object x) => Console.WriteLine("F(object)");
    static void F(int x) => Console.WriteLine("F(int)");
    static void F(double x) => Console.WriteLine("F(double)");
    static void F<T>(T x) => Console.WriteLine("F<T>(T)");            
    static void F(double x, double y) => Console.WriteLine("F(double, double)");
    
    public static void UsageExample()
    {
        F();            // Invocă F()
        F(1);           // Invocă F(int)
        F(1.0);         // Invocă F(double)
        F("abc");       // Invocă F(string)
        F((double)1);   // Invocă F(double)
        F((object)1);   // Invocă F(object)
        F<int>(1);      // Invocă F<int>(int)
        F(1, 1);        // Invocă F(double, double)
    }
}

După cum se arată în exemplu, o anumită metodă poate fi întotdeauna selectată prin folosind metoda de cast a argumentelor la tipurile exacte de parametri și argumente de tip.

4.2 Alți membri ai funcției

Membrii care conțin cod executabil sunt cunoscuți în mod colectiv ca membrii funcției ai unei clase. Secțiunea precedentă descrie metode, care sunt tipurile primare de membri ai funcției. Această secțiune descrie celelalte tipuri de membri ai funcției acceptate de C#: constructori, proprietăți, indexori, evenimente, operatori și finalizatori.

Următorul exemplu arată o clasă generică numită MyList<T>, care implementează o listă extensibilă de obiecte. Clasa conține câteva exemple ale celor mai comune tipuri de membri ai funcției.

public class MyList<T>
{
    const int DefaultCapacity = 4;

    T[] _items;
    int _count;

    public MyList(int capacity = DefaultCapacity)
    {
        _items = new T[capacity];
    }

    public int Count => _count;

    public int Capacity
    {
        get =>  _items.Length;
        set
        {
            if (value < _count) value = _count;
            if (value != _items.Length)
            {
                T[] newItems = new T[value];
                Array.Copy(_items, 0, newItems, 0, _count);
                _items = newItems;
            }
        }
    }

    public T this[int index]
    {
        get => _items[index];
        set
        {
            _items[index] = value;
            OnChanged();
        }
    }

    public void Add(T item)
    {
        if (_count == Capacity) Capacity = _count * 2;
        _items[_count] = item;
        _count++;
        OnChanged();
    }
    protected virtual void OnChanged() =>
        Changed?.Invoke(this, EventArgs.Empty);

    public override bool Equals(object other) =>
        Equals(this, other as MyList<T>);

    static bool Equals(MyList<T> a, MyList<T> b)
    {
        if (Object.ReferenceEquals(a, null)) return Object.ReferenceEquals(b, null);
        if (Object.ReferenceEquals(b, null) || a._count != b._count)
            return false;
        for (int i = 0; i < a._count; i++)
        {
            if (!object.Equals(a._items[i], b._items[i]))
            {
                return false;
            }
        }
        return true;
    }

    public event EventHandler Changed;

    public static bool operator ==(MyList<T> a, MyList<T> b) =>
        Equals(a, b);

    public static bool operator !=(MyList<T> a, MyList<T> b) =>
        !Equals(a, b);
}

4.3 Constructori

C# acceptă atât constructori de instanță, cât și constructori statici. Un constructor de instanță este un membru care implementează acțiunile necesare pentru a inițializa o instanță a unei clase. Un constructor static este un membru care implementează acțiunile necesare pentru a inițializa clasa în sine atunci când este încărcată pentru prima dată.

Un constructor este declarat ca o metodă fără tip de returnare și cu același nume ca și clasa care o conține. Dacă o declarație de constructor include un modificator static, acesta declară un constructor static. În caz contrar, declară un constructor de instanță.

Constructorii de instanță pot fi supraîncărcați și pot avea parametri opționali. De exemplu, clasa MyList<T> declară un constructor de instanță cu un singur parametru opțional int. Constructorii de instanță sunt invocați folosind operatorul new. Următoarele instrucțiuni alocă două instanțe MyList<string> folosind constructorul clasei MyList cu și fără argumentul opțional.

MyList<string> list1 = new();
MyList<string> list2 = new(10);

Spre deosebire de alți membri, constructorii de instanță nu sunt moșteniți. O clasă nu are constructori de instanță decât cei declarați efectiv în clasă. Dacă nu este furnizat niciun constructor de instanță pentru o clasă, atunci este furnizat automat unul gol fără parametri.

4.4 Proprietăți

Proprietățile sunt o extensie naturală a câmpurilor. Ambii sunt membri numiți cu tipuri asociate, iar sintaxa pentru accesarea câmpurilor și proprietăților este aceeași. Cu toate acestea, spre deosebire de câmpuri, proprietățile nu indică locații de stocare. În schimb, proprietățile au accesori care specifică instrucțiunile executate atunci când valorile lor sunt citite sau scrise. Accesorul get citește valoarea. Accesorul set scrie valoarea.

O proprietate este declarată ca un câmp, cu excepția faptului că declarația se termină cu un accesor get sau un accesor set scris între delimitatorii { and } în loc să se termine cu punct și virgulă. O proprietate care are atât un accesor get, cât și un accesor set este o proprietate de citire-scriere. O proprietate care are doar un accesor get este o proprietate numai pentru citire. O proprietate care are doar un accesor set este o proprietate numai de scriere.

Un accesor get corespunde unei metode fără parametri cu o valoare returnată de tipul proprietății. Un accesor set corespunde unei metode cu un singur parametru numit valoare și fără tip de returnare. Accesorul get calculează valoarea proprietății. Accesorul set oferă o nouă valoare pentru proprietate. Când proprietatea este ținta unei atribuiri sau operandul ++ sau --, accesorul set este invocat. În alte cazuri în care proprietatea este referită, accesorul get este invocat.

Clasa MyList<T> clasa declară două proprietăți, Count și Capacity, care sunt doar citire și, respectiv, citire-scriere. Următorul cod este un exemplu de utilizare a acestor proprietăți:

MyList<string> names = new();
names.Capacity = 100;   // Invocă accesorul get
int i = names.Count;    // Invocă accesorul get
int j = names.Capacity; // Invocă accesorul get

Similar câmpurilor și metodelor, C# acceptă atât proprietățile instanței, cât și proprietățile statice. Proprietățile statice sunt declarate cu modificatorul static, iar proprietățile instanței sunt declarate fără acesta.

Accesorii unei proprietăți pot fi virtuali. Când o declarație de proprietate include un modificator virtual, abstract sau de suprascriere, se aplică accesorilor proprietății.

4.5 Indexori

Un indexor este un membru care permite obiectelor să fie indexate în același mod ca o matrice. Un indexor este declarat ca o proprietate, cu excepția faptului că numele membrului este urmat de o listă de parametri scrisă între delimitatorii [ and ]. Parametrii sunt disponibili în accesoriile indexerului. Similar cu proprietățile, indexorii pot fi citire-scriere, doar citire și doar scriere, iar accesoriile unui indexor pot fi virtuale.

Clasa MyList<T> clasa declară un singur indexor de citire-scriere care ia un parametru int. indexorul face posibilă indexarea instanțelor MyList cu valori int. De exemplu:

MyList<string> names = new();
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
for (int i = 0; i < names.Count; i++)
{
    string s = names[i];
    names[i] = s.ToUpper();
}

Indexorii pot fi supraîncărcați. O clasă poate declara mai mulți indexori atâta timp cât numărul sau tipurile parametrilor acestora diferă.

4.6 Evenimente

Un eveniment este un membru care permite unei clase sau obiect să furnizeze notificări. Un eveniment este declarat ca un câmp, cu excepția faptului că declarația include un cuvânt cheie de eveniment și tipul trebuie să fie de tip delegat.

În cadrul unei clase care declară un membru al evenimentului, evenimentul se comportă la fel ca un câmp de tip delegat (cu condiția ca evenimentul să nu fie abstract și să nu declare accesori). Câmpul stochează o referință la un delegat care reprezintă handler-ul de evenimente care au fost adăugați la eveniment. Dacă nu se găsește nici un handler de eveniment, câmpul este nul.

Clasa MyList<T> declară un singur membru al evenimentului numit Changed, ceea ce indică faptul că un nou element a fost adăugat în listă. Evenimentul Changed este declanșat de metoda virtuală OnChanged, care verifică mai întâi dacă evenimentul este nul (înseamnă că nu se află nici un handler). Noțiunea de implementare a unui eveniment este tocmai echivalentă cu invocarea delegatului reprezentat de eveniment. Nu există restricții de limbaj speciale pentru a genera evenimente.

Clienții reacționează la evenimente prin intermediul handlerelor de evenimente. Handlerele de evenimente sunt atașați folosind operatorul += și eliminați folosind operatorul -=. Următorul exemplu atașează un handler de evenimente la evenimentul Changed al unei MyList<string>.

class EventExample
{
    static int s_changeCount;
    
    static void ListChanged(object sender, EventArgs e)
    {
        s_changeCount++;
    }
    
    public static void Usage()
    {
        var names = new MyList<string>();
        names.Changed += new EventHandler(ListChanged);
        names.Add("Liz");
        names.Add("Martha");
        names.Add("Beth");
        Console.WriteLine(s_changeCount); // "3"
    }
}

Pentru scenariile avansate în care se dorește controlul stocării de bază a unui eveniment, o declarație de eveniment poate furniza în mod explicit accesori de adăugare și eliminare, care sunt similare cu accesorul setat al unei proprietăți.

4.7 Operatori

Un operator este un membru care definește semnificația aplicării unui anumit operator de expresie la instanțe ale unei clase. Pot fi definiți trei tipuri de operatori: operatori unari, operatori binari și operatori de conversie. Toți operatorii trebuie să fie declarați ca publici și statici.

Clasa MyList<T> declară doi operatori, operator == și operator !=. Acești operatori suprascriși dau un nou sens expresiilor care aplică acești operatori instanțelor MyList. În mod specific, operatorii definesc egalitatea a două instanțe MyList<T> ca comparând fiecare dintre obiectele conținute folosind metodele Equals. Următorul exemplu utilizează operatorul == pentru a compara două instanțe MyList.

MyList<int> a = new();
a.Add(1);
a.Add(2);
MyList<int> b = new();
b.Add(1);
b.Add(2);
Console.WriteLine(a == b);  // Primim "True"
b.Add(3);
Console.WriteLine(a == b);  // Primim "False"

Prima Console.WriteLine ne va trimite True deoarece cele două liste conțin același număr de obiecte cu aceleași valori în aceeași ordine. Dacă MyList<T> operatorul nu este definit ==, prima Console.WriteLine ar avea rezultat False, deoarece a și b fac referire la diferite instanțe MyList<int>.

4.8 Finalizatori

Un finalizator este un membru care implementează acțiunile necesare pentru a finaliza o instanță a unei clase. De obicei, este necesar un finalizator pentru a elibera resursele negestionate. Finalizatoarele nu pot avea parametri, nu pot avea modificatori de accesibilitate și nu pot fi invocați în mod explicit. Finalizatorul pentru o instanță este invocat automat în timpul colectării resurselor nedorite. Pentru mai multe informații, consultați articolul despre finalizatori.

Colectorului de gunoi i se permite o mare latitudine în a decide când să colecteze obiecte și să ruleze finalizatori. Mai exact, momentul invocărilor finalizatorului nu este determinist, iar finalizatorile pot fi executate pe orice fir. Din aceste motive și din alte motive, clasele ar trebui să implementeze finalizatori numai atunci când alte soluții nu sunt convenabile.

Declarația using oferă o abordare mai bună a distrugerii obiectelor.

4.9 Expresii

Expresiile sunt construite din operanzi și operatori. Operatorii unei expresii indică ce operații se aplică operanzilor. Exemplele de operatori includ +, -, *, / și new. Exemplele de operanzi includ literali, câmpuri, variabile locale și expresii.

Când o expresie conține mai mulți operatori, precedența operatorilor controlează ordinea în care operatorii individuali sunt evaluați. De exemplu, expresia x + y * z este evaluată ca x + (y * z) deoarece operatorul * are o prioritate mai mare decât operatorul +.

Când apare un operand între doi operatori cu aceeași precedență, asociativitatea operatorilor controlează ordinea în care sunt efectuate operațiile:

Majoritatea operatorilor pot fi supraîncărcați. Supraîncărcarea operatorului permite ca implementările de operatori definiți de utilizator să fie specificate pentru operațiunile în care unul sau ambii operanzi sunt dintr-o clasă sau un tip de structură definit de utilizator.

C# oferă operatori pentru a efectua operații aritmetice, logice, pe biți și de deplasare și comparații de egalitate și ordine.

Pentru lista completă a operatorilor C# ordonați după nivelul de precedență, consultați operatori C#.

4.10 Declarații

Acțiunile unui program sunt exprimate folosind declarațiile using. C# acceptă mai multe tipuri diferite de instrucțiuni, dintre care un număr sunt definite în termeni de instrucțiuni încorporate.

Următoarea listă enumeră tipurile de declarații care pot fi utilizate:

4.11 Sarcini

TODO

Bibliografie

[1] Microsoft Corporation. C# Documentation, https://docs.microsoft.com/en-us/dotnet/csharp/, 2022.